package com.orgzly.android.espresso; import android.content.pm.ActivityInfo; import android.os.Build; import android.support.test.InstrumentationRegistry; import android.support.test.espresso.DataInteraction; import android.support.test.espresso.UiController; import android.support.test.espresso.ViewAction; import android.support.test.espresso.ViewInteraction; import android.support.test.espresso.action.CloseKeyboardAction; import android.support.test.rule.ActivityTestRule; import android.view.View; import android.widget.ListView; import android.widget.Spinner; import com.orgzly.R; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import static android.support.test.espresso.Espresso.*; import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.*; import static android.support.test.espresso.matcher.RootMatchers.isDialog; import static android.support.test.espresso.matcher.ViewMatchers.*; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.Matchers.*; /* * Few espresso-related notes: * * - closeSoftKeyboardWithDelay() is often used, as on some devices keyboard will cover the view * which is supposed to be clicked next, causing java.lang.SecurityException to get thrown. * * - replaceText() is preferred over typeText() as it is much faster. */ class EspressoUtils { static final int SETTINGS_REVERSED_NOTE_CLICK_ACTION = 1; static final int SETTINGS_STATE_KEYWORDS = 21; static final int SETTINGS_DEFAULT_PRIORITY = 22; static final int SETTINGS_LOWEST_PRIORITY = 23; static final int SETTINGS_NEW_NOTE_STATE = 25; static final int SETTINGS_CREATED_AT = 27; static final int SETTINGS_REPOS = 33; static final int SETTINGS_SYNC_AFTER = 34; static final int IMPORT_GETTING_STARTED = 39; static final int SETTINGS_CLEAR_DATABASE = 40; /** */ @SuppressWarnings("unchecked") public static DataInteraction onListItem(int pos) { return onData(anything()) .inAdapterView(allOf(withId(android.R.id.list), isDisplayed())) .atPosition(pos); } static ViewInteraction onSnackbar() { return onView(withId(android.support.design.R.id.snackbar_text)); } /* * Regular expression matching. * https://github.com/hamcrest/JavaHamcrest/issues/65 */ // static TypeSafeMatcher<String> withPattern(final String pattern) { // checkNotNull(pattern); // // return new TypeSafeMatcher<String>() { // @Override // public boolean matchesSafely(String s) { // return Pattern.compile(pattern).matcher(s).matches(); // } // // @Override // public void describeTo(Description description) { // description.appendText("a string matching the pattern '" + pattern + "'"); // } // }; // } /** * Item could either be on the action bar (visible) or in the overflow menu. */ static void onActionItemClick(int id, int resourceId) { try { onView(withId(id)).perform(click()); } catch (Exception e) { e.printStackTrace(); // Open the overflow menu OR open the options menu, // depending on if the device has a hardware or software overflow menu button. openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getTargetContext()); onView(withText(resourceId)).perform(click()); } } static void toLandscape(ActivityTestRule activityRule) { toOrientation(activityRule, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } static void toPortrait(ActivityTestRule activityRule) { toOrientation(activityRule, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } private static void toOrientation(ActivityTestRule activityRule, int requestedOrientation) { activityRule.getActivity().setRequestedOrientation(requestedOrientation); /* Not pretty, but it does seem to fix testFragments from randomly failing. */ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } static DataInteraction onSpinnerString(String value) { return onData(allOf(instanceOf(String.class), is(value))).inRoot(not(isDialog())); } static void settingsSetTodoKeywords(String keywords) { settingsSetKeywords(R.id.todo_states, keywords); } static void settingsSetDoneKeywords(String keywords) { settingsSetKeywords(R.id.done_states, keywords); } private static void settingsSetKeywords(int viewId, String keywords) { onActionItemClick(R.id.activity_action_settings, R.string.settings); onListItem(SETTINGS_STATE_KEYWORDS).perform(click()); onView(withId(viewId)).perform(replaceText(keywords), closeSoftKeyboardWithDelay()); onView(withText(R.string.ok)).perform(click()); onView(withText(R.string.yes)).perform(click()); pressBack(); } static void searchForText(String str) { onView(allOf(withId(R.id.activity_action_search), isDisplayed())).perform(click()); onView(withHint(R.string.search_hint)).perform(replaceText(str), pressKey(66)); /* TODO: Ugh. */ try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } /** * Give keyboard time to close, to avoid java.lang.SecurityException * if hidden button is clicked next. */ static ViewAction closeSoftKeyboardWithDelay() { return new ViewAction() { /** * The delay time to allow the soft keyboard to dismiss. */ private static final long KEYBOARD_DISMISSAL_DELAY_MILLIS = 1000L; /** * The real {@link CloseKeyboardAction} instance. */ private final ViewAction mCloseSoftKeyboard = new CloseKeyboardAction(); @Override public Matcher<View> getConstraints() { return mCloseSoftKeyboard.getConstraints(); } @Override public String getDescription() { return mCloseSoftKeyboard.getDescription(); } @Override public void perform(final UiController uiController, final View view) { mCloseSoftKeyboard.perform(uiController, view); uiController.loopMainThreadForAtLeast(KEYBOARD_DISMISSAL_DELAY_MILLIS); } }; } /** * Matcher for ListView with exactly specified number of items. */ static TypeSafeMatcher<View> listViewItemCount(final int count) { return new TypeSafeMatcher<View>() { @Override public boolean matchesSafely(View view) { return count == ((ListView) view).getCount(); } @Override public void describeTo(Description description) { description.appendText("a ListView which contains " + count + " item(s)"); } }; } /** * Matcher for Spinner with exactly specified number of items. */ static TypeSafeMatcher<View> spinnerItemCount(final int count) { return new TypeSafeMatcher<View>() { @Override public boolean matchesSafely(View view) { return count == ((Spinner) view).getCount(); } @Override public void describeTo(Description description) { description.appendText("a Spinner which contains " + count + " item(s)"); } }; } /** * Checks if view has a background set. * Used for checking if note is selected. */ static Matcher<View> isHighlighted() { return new TypeSafeMatcher<View>() { @Override public boolean matchesSafely(View view) { return view.getBackground() != null; } @Override public void describeTo(Description description) { description.appendText("a View which is highlighted"); } }; } }